#include <iostream>
#include <string>
#include <gmp.h>
#include <gmpxx.h>
#include <string>
#include <fstream>
#include <sstream>
#include <pthread.h>
#include <list>
#include <semaphore.h>
#include <cstdio>


#define BITS_PER_DIGIT 3.32192809488736234787

int liter = 22;

// Variaveis globais

bool req_ab = false;
pthread_mutex_t req_abm; // comeca com 0
pthread_mutex_t labm; // comecao com 1
std::list<mpf_class> lat;
std::list<mpf_class> lapi;
std::list<mpf_class> lb;

pthread_mutex_t lpm; // comeca com 1
std::list<mpf_class> lp;
pthread_mutex_t ltm;
std::list<mpf_class> lt;

pthread_mutex_t req_tm; // comeca com 0
bool req_t = false;
std::list<mpf_class> lpi;


// mpf_class *a0, *a1, *b0, *b1, *t0, *t1, *p0, *p1, *pi0, *pi1;

bool running = true;

int cmpr;
int iteration;

void * fthread_p (void * noa) {
	mpf_class p(1);
	pthread_mutex_lock(&lpm);
	{ // Comeco da regiao critica
		lp.push_back(mpf_class(p));
	} // Final da regiao critica
	pthread_mutex_unlock(&lpm);

	for (int i = 0; i <= liter; ++i)
	{
		p *= 2;
		pthread_mutex_lock(&lpm);
		{ // Comeco da regiao critica
			lp.push_back(mpf_class(p));
		} // Final da regiao critica
		pthread_mutex_unlock(&lpm);
	}
	return 0;
}


mpf_class * a_val;
mpf_class * an_val;
mpf_class * b_val;
mpf_class * bn_val;

void * fthread_ab_a (void * noa) {
	*an_val = (*a_val + *b_val) / 2;
	return 0;
}

void * fthread_ab_b (void * noa) {
	*bn_val = sqrt(*a_val * *b_val);
	return 0;
}

void * fthread_ab (void* noa) {
	a_val = new mpf_class(1);
	b_val = new mpf_class(sqrt(mpf_class(0.5)));
	an_val = new mpf_class();
	bn_val = new mpf_class();

	pthread_t tthread_ab_a;
	pthread_t tthread_ab_b;

	for (int i = 0; i <= liter; i++) {
		pthread_create(&tthread_ab_a, 0, fthread_ab_a, 0);
		pthread_create(&tthread_ab_b, 0, fthread_ab_b, 0);

		pthread_join(tthread_ab_a, 0);
		pthread_join(tthread_ab_b, 0);

		std::swap(a_val, an_val);
		std::swap(b_val, bn_val);

		pthread_mutex_lock(&labm);
		{
			lat.push_back(mpf_class(*a_val));
			lapi.push_back(mpf_class(*a_val));
			lb.push_back(mpf_class(*b_val));
			// printf("A and B #%d have been calculated!\n", i);
			if (req_ab) {
				req_ab = false;
				pthread_mutex_unlock(&req_abm);
			}
		}	
		pthread_mutex_unlock(&labm);
	}

	delete a_val;
	delete b_val;
	delete an_val;
	delete bn_val;

	return 0;
}

void * fthread_t (void *) {
	mpf_class tmp; // Eh possivel paralelilzar mais, criando outra thread para o tmp
	mpf_class old_a[2];
	int who = 1;
	old_a[0] = 1;
	mpf_class t(0.25);
	mpf_class p;

	for (int i = 0; i <= liter; ++i) {
		bool tryAgain = true;
		while (tryAgain) {
			pthread_mutex_lock(&labm);
			{
				if (lat.empty()) {
					req_ab = true;
				} else {
					old_a[who] = lat.front();
					lat.pop_front();
					tryAgain = false;
				}
			}
			pthread_mutex_unlock(&labm);
			if (tryAgain) pthread_mutex_lock(&req_abm);
			else {
				pthread_mutex_lock(&lpm);
				{
					p = lp.front();
					lp.pop_front();
				}
				pthread_mutex_unlock(&lpm);
				tmp = old_a[!who] - old_a[who];
				who = !who;
				t -= p * (tmp * tmp);

				pthread_mutex_lock(&ltm);
				{
					lt.push_back(t);
					// printf("t #%d has been calculated!\n", i);
					if (req_t) {
						req_t = false;
						pthread_mutex_unlock(&req_tm);
					}
				}
				pthread_mutex_unlock(&ltm);
			}
		}
	}

	return 0;
}

void * fthread_pi(void *) {
	mpf_class tmp;
	mpf_class pi;
	mpf_class t;
	mpf_class eq_cmp;
	eq_cmp = mpf_class("1E-10000000");

	for (int i = 0; i <= liter; i++) {
		bool tryAgain = true;
		while (tryAgain) {
			pthread_mutex_lock(&ltm);
			{
				if (lt.empty()) {
					req_t = true;
				} else {
					t = lt.front();
					lt.pop_front();
					tryAgain = false;
				}
			}
			pthread_mutex_unlock(&ltm);
			if (tryAgain) pthread_mutex_lock(&req_tm);
			else {
				pthread_mutex_lock(&labm);
				{
					tmp = lapi.front();
					tmp += lb.front();
					lb.pop_front();
					lapi.pop_front();
				}
				pthread_mutex_unlock(&labm);

				pi = (tmp * tmp) / (4 * t);
				printf("Iteracao %d calculada com sucesso!\n", i+1);

				lpi.push_back(pi);
			}
		}
	}

	// std::cout << pi << std::endl;

	return 0;
}

// void * threadWriter (void* noa) {
	// std::stringstream ss;
	// ss << "GLM_PI_It_" << iteration << ".txt";
	// std::fstream file;
	// file.open(ss.str().c_str(), std::fstream::out);
	// file.setf(std::ios::fixed, std::ios::floatfield);
	// file.precision(10000000);
	// file << *pi0 << std::endl;
	// return 0;
// }

void gauss_legendre() {

	// pthread_mutex_t req_abm; // comeca com 0
	// pthread_mutex_t labm; // comecao com 1
	// pthread_mutex_t lpm; // comeca com 1
	// pthread_mutex_t req_tm; // comeca com 0
	// pthread_mutex_t ltm; // comecao com 1
	pthread_mutex_init(&req_abm, 0);
	pthread_mutex_init(&labm, 0);
	pthread_mutex_init(&lpm, 0);
	pthread_mutex_init(&ltm, 0);
	pthread_mutex_init(&req_tm, 0);

	pthread_mutex_lock(&req_tm);
	pthread_mutex_lock(&req_abm);

	req_ab = false;
	req_t = false;
	
	lat.clear();
	lapi.clear();
	lb.clear();
	lp.clear();
	lt.clear();
	lpi.clear();


	pthread_t tthread_pi;
	pthread_t tthread_p;
	pthread_t tthread_ab;
	pthread_t tthread_t;


	pthread_create(&tthread_p, 0, fthread_p, 0);
	pthread_create(&tthread_ab, 0, fthread_ab, 0);
	pthread_create(&tthread_t, 0, fthread_t, 0);
	pthread_create(&tthread_pi, 0, fthread_pi, 0);

	pthread_join(tthread_pi, 0);
	
	{
		int it = 1;
		std::list<mpf_class>::iterator itA, itB;
		itA = lpi.begin();
		itB = lpi.end();
		for(;itA!=itB;itA++) {
			std::stringstream ss;
			ss << "GLMTCP_Iteracao_" << it++ << ".txt";
			std::fstream file;
			file.open(ss.str().c_str(), std::fstream::out);
			file.setf(std::ios::fixed, std::ios::floatfield);
			file.precision(10000000);
			file << (*itA) << std::endl;
		}
	}


	std::cout << "PI calculado com sucesso!\n";
}

int main(int argc, char *argv[]) {
	mpf_set_default_prec((long int)(3.32192809488736234787 * 10000100.0));

	std::cout << "Bem vindo ao programa que calcula o PI!\n";
	std::cout << "10 milhoes de casas do pi serao calculadas!\n";
	std::cout << "Arquivos de prefixo \"GLMTCP_\" serao criados\n";
	std::cout << "Todos os arquivos serao criados apenas ao\n\tfinal da execucao do programa!\n";

	gauss_legendre();
	return 0;
}
